import json
import os
import sys
import getopt

global cwd
cwd = os.getcwd()
global abs_path
abs_path = os.path.abspath(os.path.dirname(__file__)) 
if cwd != abs_path:
    os.chdir(abs_path)

basic_type = ["float", "uint32", "bool", "int32", "string"]

container_key_type = {"float":"float", "uint32":"std::uint32_t", "bool":"bool", "int32":"std::int32_t", "string":"std::string"}

class NodeGenerator:
    def __init__(self, file_path, out_dir):
        self.file_path = file_path
        self.file_name = self.get_filename_without_extension(file_path)
        self.out_dir = out_dir
        self.gen_plato_base_cpp()

    def get_container_key(self, type):
        return container_key_type[type]

    def get_default_value(self, var):
        type = var["type"]
        if self.is_basic_type(type):
            if type == "string":
                return '"' +var["default"]+'"'
            elif type == "bool":
                if var["default"]:
                    return "true"
                else:
                    return "false"
            elif type == "float":
                return 'float('+str(var["default"])+')'
            else:
                return str(var["default"])

    def gen_var_creator(self, cont):
        include_headers = []
        file_hh = open(os.path.join(self.out_dir,self.file_name+'_var') +".hh", "w")
        file_hh.write('#pragma once\n\n')
        file_hh.write('#include "../core/plato_variable.hh"\n\n')        
        file_hh.write('namespace plato { namespace ' + self.file_name + '{\n\n')
        file_hh.write('extern auto create_variable(plato::VarID var_id, DomainPtr domain_ptr, plato::PlatoVariableSyncType sync_type) -> VariablePtr;\n')
        file_hh.write('extern auto query_variable_size(plato::VarID var_id) -> std::size_t;\n')
        file_hh.write('extern auto get_var_name(plato::VarID var_id) -> const std::string&;\n\n')
        file_hh.write('}}\n\n')
        file_cpp = open(os.path.join(self.out_dir,self.file_name+'_var') +".cpp", "w")
        file_cpp.write('#include "' + self.file_name +'_var.hh"\n')
        file_cpp.write('#include "plato_node_factory.hh"\n')
        file_cpp.write('#include <unordered_map>\n')
        file_cpp.write('#include <string>\n')
        for var in cont['variables']:
            if not self.is_basic_type(var['type']) and not (var['type'] in include_headers):
                if var['type'] != 'seq' and var['type'] != 'set' and var['type'] != 'dict':
                    file_cpp.write('#include "'+var['type']+'.hh"\n')
                    include_headers.append(var['type'])
        file_cpp.write('\n')
        file_cpp.write('static std::unordered_map<plato::VarID, std::size_t> var_creator_size_map = {\n')
        for var in cont['variables']:
            if var['type'] == "seq":
                file_cpp.write('  {'+str(var['id'])+', sizeof(plato::Array<plato::'+var['key'].capitalize()+'>)},\n')
            elif var['type'] == "set":
                file_cpp.write('  {'+str(var['id'])+', sizeof(plato::Set<'+self.get_container_key(var['key'])+'>)},\n')
            elif var['type'] == "dict":
                file_cpp.write('  {'+str(var['id'])+', sizeof(plato::Map<'+self.get_container_key(var['key'])+',plato::'+var['value'].capitalize()+'>)},\n')
            else:
                if self.is_basic_type(var['type']):
                    file_cpp.write('  {'+str(var['id'])+', sizeof(plato::'+var['type'].capitalize()+')},\n')
                else:
                    file_cpp.write('  {'+str(var['id'])+', plato::'+var['type'].capitalize()+'::get_fixed_mem_size()},\n')
        file_cpp.write('};\n\n')
        file_cpp.write('static std::unordered_map<plato::VarID, std::string> var_id_name_map = {\n')
        for var in cont['variables']:
            file_cpp.write('  {'+str(var['id'])+',"'+var['title']+'"},\n')
        file_cpp.write('};\n\n')
        file_cpp.write('\nnamespace plato { namespace '+self.file_name+' {\n\n')
        file_cpp.write('using VarCreator = VariablePtr(*)(DomainPtr, plato::PlatoVariableSyncType);\n\n')
        for var in cont["variables"]:
            file_cpp.write('auto create_'+var["title"]+'(DomainPtr domain_ptr, plato::PlatoVariableSyncType sync_type) -> VariablePtr {\n')
            if var['type'] == 'seq':
                file_cpp.write('  auto ptr = domain_ptr->NewNoIdentified<Array<'+var['key'].capitalize()+'>>(sync_type);\n')
            elif var['type'] == 'set':
                file_cpp.write('  auto ptr = domain_ptr->NewNoIdentified<Set<'+self.get_container_key(var['key'])+'>>(sync_type);\n')
            elif var['type'] == 'dict':
                file_cpp.write('  auto ptr = domain_ptr->NewNoIdentified<Map<'+self.get_container_key(var['key'])+','+var['value'].capitalize()+'>>(sync_type);\n')
            else:
                file_cpp.write('  auto ptr = domain_ptr->NewNoIdentified<'+var['type'].capitalize()+'>(sync_type);\n')
            file_cpp.write('  auto impl_ptr = std::dynamic_pointer_cast<VariableImpl>(ptr);\n')
            file_cpp.write('  impl_ptr->set_id('+str(var['id'])+');\n')
            if self.is_basic_type(var['type']):
                if "default" in var:
                    file_cpp.write('  ptr->set_default('+self.get_default_value(var)+');\n')
            file_cpp.write('  domain_ptr->identify(ptr);\n')
            file_cpp.write('  return ptr;\n')
            file_cpp.write('}\n\n')
        file_cpp.write('static std::unordered_map<plato::VarID, VarCreator> var_creator_map = {\n')
        for var in cont['variables']:
            file_cpp.write('  {')
            file_cpp.write(str(var['id'])+', create_'+var["title"])
            file_cpp.write('},\n')
        file_cpp.write('};\n\n')        
        file_cpp.write('}}\n\n')
        file_cpp.write('auto plato::'+self.file_name+'::create_variable(plato::VarID var_id, DomainPtr domain_ptr, plato::PlatoVariableSyncType sync_type) -> VariablePtr {\n')
        file_cpp.write('  auto it = var_creator_map.find(var_id);\n')
        file_cpp.write('  if (it == var_creator_map.end()) { return nullptr; }\n')
        file_cpp.write('  return (*(it->second))(domain_ptr, sync_type);\n')
        file_cpp.write('}\n\n')
        file_cpp.write('auto plato::'+self.file_name+'::query_variable_size(plato::VarID var_id) -> std::size_t {\n')
        file_cpp.write('  auto it = var_creator_size_map.find(var_id);\n')
        file_cpp.write('  if (it == var_creator_size_map.end()) { return 0; }\n')
        file_cpp.write('  return it->second;\n')
        file_cpp.write('}\n\n')
        file_cpp.write('auto plato::'+self.file_name+'::get_var_name(plato::VarID var_id) -> const std::string& {\n')
        file_cpp.write('  static std::string NullString;\n')
        file_cpp.write('  auto it = var_id_name_map.find(var_id);\n')
        file_cpp.write('  if (it == var_id_name_map.end()) { return NullString; }\n')
        file_cpp.write('  return it->second;\n')
        file_cpp.write('}\n\n')
        file_cpp.write('extern auto add_var_creator(const char* name, VarCreatorFunc func, VarCreatorSizeFunc size_func) -> bool;\n')
        file_cpp.write('bool res_'+self.file_name+' = add_var_creator("'+ self.file_name +'", plato::'+self.file_name+'::create_variable, plato::'+self.file_name+'::query_variable_size);\n')
        file_cpp.write('extern auto add_var_name_query(const char *name, VarNameQueryFunc func) -> bool;\n')
        file_cpp.write('bool query_'+self.file_name+' = add_var_name_query("'+self.file_name+'", plato::'+self.file_name+'::get_var_name);\n')

    def gen_plato_base_cpp(self):
        file = open(self.file_path).read()
        cont = json.loads(file)
        self.gen_var_creator(cont)

    def get_filename_without_extension(self, file_path):
        file_basename = os.path.basename(file_path)
        return file_basename.split('.')[0]

    def get_var_type(self, str_type):
        return str_type.capitalize()

    def is_basic_type(self, str_type):
        return str_type in basic_type

    def gen_node_pin_var_mem_size(self, file, pin):
        var_type = pin["type"]
        if var_type != 'exec':
            if var_type == 'seq':
                file.write('    var_mem_size_ += sizeof(Array<'+self.get_var_type(pin['key'])+'>);\n')
            elif var_type == 'set':
                file.write('    var_mem_size_ += sizeof(Set<'+self.get_var_type(pin['key'])+'>);\n')
            elif var_type == 'dict':
                file.write('    var_mem_size_ += sizeof(Map<'+self.get_var_type(pin['key'])+','+self.get_var_type(pin['value'])+'>);\n')
            else:
                file.write('    var_mem_size_ += sizeof('+self.get_var_type(var_type)+');\n')

if __name__ == "__main__":
    opts = None
    args = None
    opts,args = getopt.getopt(sys.argv[1:],'-f:-o:',['file=','out='])
    file_path = "."
    out_dir = "."
    for opt, value in opts:
        if opt in ("-o", "--out"):
            out_dir = value
        elif opt in ("-f", "--file"):
            file_path = value
    if os.path.exists(file_path):
        NodeGenerator(file_path, out_dir)
    # try:
    #     opts,args = getopt.getopt(sys.argv[1:],'-f:-o',['file=','out='])
    #     file_path = "."
    #     out_dir = "."
    #     for opt, value in opts:
    #         if opt in ("-o", "--out"):
    #             out_dir = value
    #         elif opt in ("-f", "--file"):
    #             file_path = value
    #     if os.path.exists(file_path):
    #         NodeGenerator(file_path, out_dir)
    # except getopt.GetoptError as e:
    #     print(str(e))
